/*
 * Copyright (c) 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Name: UarReport.java
 *
 * Description: Command line application to display the defined User Added
 * Results (UARs)
 *
 * The class uses the Google Admin API for java. This requires the appliance to
 * be at least of software version 6.0. It will pull the User Added Results
 * informationfrom the specified GSA and display details about the defined
 * UARs.  It will optionally test if the UARs' associated URLs are able to be
 * fetched.
 *
 * Please note that this class needs the following GData libraries:
 * - gdata-core-1.0.jar
 * - gdata-gsa-1.0.jar
 * - google-collect1.0-rc1.jar
 *
 * The libraries can be downloaded from:
 * http://code.google.com/p/google-enterprise-gdata-api/
 *
 * This class also needs the following Apache Commons library:
 * - commons-cli-1.2.jar
 *
 * This library can be downloaded from:
 * http://commons.apache.org/cli/download_cli.cgi
 *
 * To run the program use the following two commands:
 * 1) From the project's top level directory (the directory that contains the
 *   'src' folder) compile the project by running
 *   ant deploy
 *
 * 2) execute
 *   java -jar uar.jar -h gsa1 -u admin -p secret
 *
 * Please note that the parameter settings are just examples. You will have to
 * replace them with your local appliance values.
 *
 * The usage message:
 * usage: java -jar uar.jar [options]
 *  -c,--csv               print results in CSV format instead of normal
 *                         report.
 *  -h,--hostname <host>   the IP address or hostname of the GSA
 *  -P,--port <portNum>    the port number of the GSA's admin interface.  If
 *                         not provided, defaults to 8000.
 *  -p,--password <pw>     the user's password.  If not provided, the user
 *                         will be prompted for one.
 *  -u,--username <user>   an administrator's username
 *  -v,--verify            verify that the URLs are valid.
 */
package com.google.enterprise.gsa.uar;

import com.google.enterprise.apis.client.ImprovedGsaClient;
import com.google.enterprise.apis.client.ImprovedGsaClientFactory;
import com.google.enterprise.gsa.exception.ExportConfigFailedException;
import com.google.enterprise.gsa.util.GsaConfigExporter;
import com.google.enterprise.gsa.util.PasswordPrompt;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.ServiceException;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;

import static java.lang.System.err;
import static java.lang.System.exit;
import static java.lang.System.out;

import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Scanner;

/**
 * Creates a report for User Added Results on a GSA.<p />
 * For example:<pre>
 * Query                URL                  Description            Added By Link Status
 * -------------------------------------------------------------------------------------
 * test                 http://test/         test                     treedy OK (200)
 * test                 http://httP://=baba  test2                    jimbob Bad (404)
 * balance              http://www.google.co Balance Balls             toddr Bad (404)
 * enterprise           http://en.wikipedia. USS Enterprise             anon OK (200)
 * </pre>
 */
public class UarReport {

  private static final char CSV_OPT = 'c';
  private static final char PASS_OPT = 'p';
  private static final char PORT_OPT = 'P';
  private static final char USER_OPT = 'u';
  private static final char HOST_OPT = 'h';
  private static final char VERIFY_OPT = 'v';

  public UarReport() { }

  /**
   * Parses out the UAR strings from an exported GSA config.  Only
   * gets the data between the {@code <uar_data><![CDATA[} and the
   * {@code ]]></uar_data} markers in the config file.
   *
   * @param xmlConfig an entire GSA exported
   * @return One long string that contains only UAR data
   */
  public String getRawUarDataFromConfig(final String xmlConfig) {
    if (xmlConfig == null || xmlConfig.isEmpty()) {
      return null;
    }
    final String UAR_START_TAG = "<uar_data><![CDATA[";
    final String UAR_END_TAG = "]]></uar_data";
    int startIndex = xmlConfig.indexOf(UAR_START_TAG);
    if (startIndex > -1) {
      startIndex += UAR_START_TAG.length();
    } else {
      return null;
    }

    int endIndex = xmlConfig.indexOf(UAR_END_TAG);
    if (endIndex >= startIndex) {
      return  xmlConfig.substring(startIndex, endIndex).trim();
    } else {
      return null;
    }
  }

  /**
   * Parses the UAR data out of an exported GSA config
   * @param xmlConfig a GSA exported config as a string
   * @return a list of all the UARs on the GSA
   */
  public ArrayList<Uar> getUarsFromConfig(final String xmlConfig){
    String rawData = getRawUarDataFromConfig(xmlConfig);
    if (rawData == null || rawData.length() < 4) {
      return null;
    }

    Scanner lineScanner = new Scanner(rawData).useDelimiter("\\n");

    if (lineScanner == null) {
      return null;
    }
    Scanner sc = null;
    ArrayList<Uar> uars = new ArrayList<Uar>();
    while (lineScanner.hasNext()) {
      sc = new Scanner(lineScanner.next()).useDelimiter("\\|,\\|");

      String query = sc.next();
      String url = sc.next();
      String desc = sc.next();
      String auth = sc.next();
      uars.add(new Uar(query, url, desc, auth));
    }
    return uars;
  }

  /**
   * @param args command-line arguments
   */
  public static void main(String[] args) {
    Options options = setUpOptions();
    CommandLineParser parser = new PosixParser();

    String username = null;
    String password = null;
    String hostname = null;
    int port = 8000;

    boolean printCSV = false;
    boolean verifyURLs = false;

    //
    // Set the variables from the CLI arguments
    //
    CommandLine line = null;
    try {
      line = parser.parse(options, args);
    } catch (ParseException e) {
      err.println(e.getMessage());
      HelpFormatter formatter = new HelpFormatter();
      formatter.printHelp("java -jar uar.jar [options]", options);
      exit(1);
    }

    username = line.getOptionValue(USER_OPT);
    hostname = line.getOptionValue(HOST_OPT);
    //
    // Get the optional password from the CL
    //
    if (line.hasOption(PASS_OPT)) {
      password = line.getOptionValue(PASS_OPT);
    } else {
      password = PasswordPrompt.getPasswordViaConsole("Enter password for " + username + ": ");
    }

    //
    // Get the optional port from the CL
    //
    if (line.hasOption(PORT_OPT)) {
      port = Integer.parseInt(line.getOptionValue(PORT_OPT));
    }

    //
    // Get the optional CSV and URL verification flags from the CL
    //
    printCSV = line.hasOption(CSV_OPT);
    verifyURLs = line.hasOption(VERIFY_OPT);

    ImprovedGsaClient client = null;
    try {
      client = ImprovedGsaClientFactory.create(hostname, port,
          username, password);

    } catch (AuthenticationException e) {
      printErrorAndExit(e, 1);
    }

    String exportedConfig = null;
    try {
      exportedConfig = GsaConfigExporter.getExportedConfig(client, "password");
    } catch (MalformedURLException e) {
      printErrorAndExit(e, 1);
    } catch (ExportConfigFailedException e) {
      printErrorAndExit(e, 1);
    } catch (ServiceException e) {
      err.println("Error while accessing GSA Admin API service.");
      printErrorAndExit(e, 1);
    } catch (IOException e) {
      err.println("There was an IO error while reading the GSA's config");
      printErrorAndExit(e, 1);
    }
    ArrayList<Uar> uars = new UarReport().getUarsFromConfig(exportedConfig);

    if (uars == null || uars.size() < 1) {
      out.println("No User Added Results found.");
      exit(0);
    }

    //
    // Print out the results
    //
    printHeader(printCSV);
    for (Uar uar : uars) {
      printUar(uar, printCSV, verifyURLs);
    }
  }

  /*
   * Sets all the CLI flags and options (and their defaults if appropriate).
   */
  @SuppressWarnings("static-access")
  private static Options setUpOptions() {
    Options opts = new Options();
    opts.addOption(OptionBuilder.withArgName("pw").hasArg()
        .withLongOpt("password").withDescription("the user's password.  " +
        "If not provided, the user will be prompted for one.").create(PASS_OPT));
    opts.addOption(OptionBuilder.withArgName("host").hasArg().isRequired()
        .withLongOpt("hostname").withDescription("the IP address or hostname of the GSA")
        .create(HOST_OPT));
    opts.addOption(OptionBuilder.withArgName("user").hasArg().isRequired()
        .withLongOpt("username").withDescription("an administrator's username")
        .create(USER_OPT));
    opts.addOption(OptionBuilder.withArgName("portNum").hasArg()
        .withLongOpt("port").withDescription("the port number of the GSA's " +
            "admin interface.  If not provided, defaults to 8000.")
        .create(PORT_OPT));
    opts.addOption(OptionBuilder.withLongOpt("csv")
        .withDescription("print results in CSV format instead of normal report.")
        .create(CSV_OPT));
    opts.addOption(OptionBuilder.withLongOpt("verify")
        .withDescription("verify that the URLs are valid.")
        .create(VERIFY_OPT));
    return opts;
  }

  /*
   * Prints the output header like
   *
   * Query    URL   Description   Added By   Link Status
   * ---------------------------------------------------
   *
   * or for CSV format
   *
   * Query,URL,Description,Added By,Link Status
   * -------------------------------------------
   */
  private static void printHeader(boolean printCSV) {
    String headerStr = String.format(getFormatString(printCSV), "Query", "URL",
        "Description", "Added By", "Link Status");
    out.println(headerStr);
    if (!printCSV) {
      int width = 0;
      int headerLength = headerStr.length();
      do {
        out.print('-');
        width++;
      } while (width < headerLength); // Add one char for each space
      out.println();
    }
  }

  /*
   * Prints out the information about a given UAR in the correct format
   */
  private static void printUar(Uar uar, boolean printCSV, boolean verifyURL) {
    out.println(String.format(getFormatString(printCSV), uar.getQuery(),
        uar.getUrl(), uar.getTitle(), uar.getAddedBy(),
        verifyURL ? uar.getUrlStatusString() : "Not tested"));
  }

  private static String getFormatString(boolean printCSV) {
    final String FMT_STR = "%-20s %-20.20s %-20.20s %10.10s %5s";
    final String CSV_FMT_STR = "%s,%s,%s,%s,%s";
    return printCSV ? CSV_FMT_STR : FMT_STR;
  }
  
  private static void printErrorAndExit(Exception e, int exitCode) {
    err.println(e.getLocalizedMessage());
    exit(exitCode);
  }

}
